﻿using System;
using System.Threading;

namespace _5E.Infrastructure.APM
{
    internal class AsyncResultNoResult : IAsyncResult
    {
        private const Int32 STATE_PENDING = 0;
        private const Int32 STATE_COMPLETED_SYNCHRONOUSLY = 1;
        private const Int32 STATE_COMPLETED_ASYNCHRONOUSLY = 2;
        private readonly AsyncCallback m_AsyncCallback;
        private readonly Object m_AsyncState;
        private Int32 m_CompletedState = STATE_PENDING;
        private ManualResetEvent m_AsyncWaitHandle;
        private Exception m_exception;

        public AsyncResultNoResult(AsyncCallback asyncCallback, Object state)
        {
            m_AsyncCallback = asyncCallback;
            m_AsyncState = state;
        }

        public void SetAsCompleted(Exception exception, Boolean completedSynchronously)
        {
            // Passing null for exception means no error occurred. 
            // This is the common case
            m_exception = exception;

            // The m_CompletedState field MUST be set prior calling the callback
            Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
               completedSynchronously ? STATE_COMPLETED_SYNCHRONOUSLY : STATE_COMPLETED_ASYNCHRONOUSLY);
            if (prevState != STATE_PENDING)
                throw new InvalidOperationException("You can set a result only once");

            // If the event exists, set it
            if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();

            // If a callback method was set, call it
            if (m_AsyncCallback != null) m_AsyncCallback(this);
        }

        public void EndInvoke()
        {
            // This method assumes that only 1 thread calls EndInvoke 
            // for this object
            if (!IsCompleted)
            {
                // If the operation isn't done, wait for it
                AsyncWaitHandle.WaitOne();
                AsyncWaitHandle.Close();
                m_AsyncWaitHandle = null;  // Allow early GC
            }

            // Operation is done: if an exception occured, throw it
            if (m_exception != null) throw m_exception;
        }

        #region Implementation of IAsyncResult
        public Object AsyncState { get { return m_AsyncState; } }

        public Boolean CompletedSynchronously
        {
            get { return Thread.VolatileRead(ref m_CompletedState) == STATE_COMPLETED_SYNCHRONOUSLY; }
        }

        public WaitHandle AsyncWaitHandle
        {
            get
            {
                if (m_AsyncWaitHandle == null)
                {
                    Boolean done = IsCompleted;
                    ManualResetEvent resetEvent = new ManualResetEvent(done);
                    if (Interlocked.CompareExchange(ref m_AsyncWaitHandle, resetEvent, null) != null)
                    {
                        // Another thread created this object's event; dispose 
                        // the event we just created
                        resetEvent.Close();
                    }
                    else
                    {
                        if (!done && IsCompleted)
                        {
                            // If the operation wasn't done when we created 
                            // the event but now it is done, set the event
                            m_AsyncWaitHandle.Set();
                        }
                    }
                }
                return m_AsyncWaitHandle;
            }
        }

        public Boolean IsCompleted
        {
            get { return Thread.VolatileRead(ref m_CompletedState) != STATE_PENDING; }
        }
        #endregion
    }
}
